package webserver;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;

// <editor-fold defaultstate="collapsed" desc=" UML Marker "> 
// #[regen=yes,id=DCE.1F291F92-CEBA-DA62-EE16-EA1A346A093D]
// </editor-fold>
/**
 * Class Server represents the Main Class of the Webserver
 * It accepts and manages incoming Connections
 * 
 */
public class Server extends Thread {

	// Variables
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.9D3AFDB5-D718-E8B8-A511-9B63265AB3E2]
	// </editor-fold>
	private int port;
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.1FBA4BEF-B724-6156-14B5-DEF9734E12F2]
	// </editor-fold>
	private int connections;
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.43067EE4-919E-4AA3-B930-8D09DA43D725]
	// </editor-fold>
	private volatile ThreadPool responsePool;
	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.3ACD942B-FFDE-C01C-CEDB-50E535FFE25F]
	// </editor-fold>
	private volatile ArrayList<RequestThread> requests;
	private boolean finish = false;
	private CacheManagement cacheManagement;
	private ServerSocket sock;
	private HashMap<Socket, Integer> socketCount;

	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.2DA861EF-D4F0-DE8D-A3D1-18709E4E3369]
	// </editor-fold>
	/**
	 * Server Default Constructor
	 */
	public Server() {
		this.socketCount = new HashMap<Socket, Integer>();
		this.port = AppProperties.getInstance().getPort();
		this.cacheManagement = new CacheManagement(AppProperties.getInstance().getCacheManagementInterval());
		this.requests = new ArrayList<RequestThread>();
		this.responsePool = new ThreadPool(AppProperties.getInstance().getResponseThreadCount());
	}

	/**
	 * The Run Method for Server
	 * Waits for incoming Connections and delegates new Connections to new Threads
	 */
	@Override
	public void run() {
		try {
			// Open Socket
			this.sock = new ServerSocket(this.port);

			while (!this.finish) {
				// Wait for incoming Connections
				RequestThread rt = new RequestThread(this, this.sock.accept());
				this.requests.add(rt);
				this.connections++;
			}

			if (!this.sock.isClosed()) {
				this.sock.close();
			}
		} catch (IOException io) {
			Logger.writeToLog("IO Error reading Port: " + this.port + ": " + io.toString());
		}
	}

	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,regenBody=yes,id=DCE.EB6AB258-C35F-DCFE-1FD9-0606429DD4AC]
	// </editor-fold>
	/**
	 * Get the number of Connections currently served
	 * @return int
	 */
	public int getConnections() {
		return connections;
	}

	// <editor-fold defaultstate="collapsed" desc=" UML Marker ">
	// #[regen=yes,id=DCE.A6FD7DA4-36DA-A05C-D7DF-922948341A74]
	// </editor-fold>
	/**
	 * Add a new Request to the pending list
	 * This signals that the informations have been parsed out of the input stream
	 * @param rt - The Thread handling the Request
	 * @param val - The Request itself
	 */
	public synchronized void addNewRequest(RequestThread rt, Request val) {
		// Increment used-count for the Socket
		int current = 0;
		if (this.socketCount.containsKey(val.getSocket())) {
			current = this.socketCount.get(val.getSocket()).intValue();
		}
		Integer next = new Integer(current + 1);
		this.socketCount.put(val.getSocket(), next);

		if (this.requests.contains(rt)) {
			// Signalise CacheManagement to check for precached files
			this.cacheManagement.scanForPreCaching(val);
			// Add to response Pool and let him send the Response
			ResponseThread repT = new ResponseThread(this, val);
			this.responsePool.addToWorkQueue(repT);
		}
	}

	/**
	 * A Request has been processed completely (Request, Response)
	 * Check if the connection must be kept alive or should be closed
	 * @param rt - RequestThread processing the Request
	 * @param r - The Request which has finished
	 */
	public synchronized void finishedRequest(RequestThread rt, Request r) {
		this.requests.remove(rt);

		// Check if anyone still uses this Socket otherwise close
		if (!this.socketCount.containsKey(r.getSocket()) || this.socketCount.get(r.getSocket()).intValue() == 0) {
			rt.finish();
			this.socketCount.remove(r.getSocket());
			this.connections--;
			return;
		}

		// Check if Request should be kept alive
		if (r.getKeepAlive() && r.isActive()) {
			RequestThread nrt = new RequestThread(this, r);
			this.requests.add(nrt);
		}
	}

	/**
	 * A Request has been finished completely
	 * Check if the connection must be kept alive or should be closed
	 * @param rt
	 * @param r
	 */
	public synchronized void finishedRequest(ResponseThread rt, Request r) {
		if (r instanceof HTTP10Request && !r.getKeepAlive()) {
			rt.finish();
			this.socketCount.remove(r.getSocket());
			this.connections--;
			return;
		} else {
			int current = this.socketCount.get(r.getSocket()).intValue();
			Integer next = new Integer(current - 1);
			this.socketCount.put(r.getSocket(), next);
		}
	}

	/**
	 * Tell the Server to finish
	 */
	public void finish() {
		Logger.writeToLog("Server finish triggered by user");
		Logger.writeToLog("Open Requests: " + this.requests.size() + " - Connection count: " + this.connections);
		this.finish = true;
		this.responsePool.finishAll();

		try {
			this.sock.close();
			this.interrupt();
		} catch (IOException ex) {
			Logger.writeToLog("IO Error closing Socket: " + ex.toString());
		} catch (SecurityException se) {
			// ignore :)
		}
	}

	/**
	 * Restart the Server
	 */
	public void restart() {
		this.finish = false;
		this.start();
	}
}

